﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PServiceBus.Core.Interface;
using PServiceBus.Core.Manager;
using System.Collections;

namespace PServiceBus.Core.Runtime.Topics {
    /// <summary>
    /// Management of subscriptions
    /// </summary>
    [Serializable]
    public class SubscriptionCollection : IIdentifiable {
        private Dictionary<Guid, Dictionary<Guid, ISubscriber>> _subscriptions;
        private List<Guid> _wildCardTopics;

        public SubscriptionCollection() {
            ID = Guid.NewGuid();
            _subscriptions = new Dictionary<Guid, Dictionary<Guid, ISubscriber>>();
            _wildCardTopics = new List<Guid>();
        }

        public Dictionary<Guid, Dictionary<Guid, ISubscriber>> SubscriptionDict {
            get { return _subscriptions; }
            set { _subscriptions = value; }
        }

        public List<Guid> WildCardTopics {
            get { return _wildCardTopics; }
            set { _wildCardTopics = value; }
        }

        /// <summary>
        /// Create a new instance of subscription collection
        /// </summary>
        internal static SubscriptionCollection New {
            get { return new SubscriptionCollection(); }
        }
        /// <summary>
        /// Retrieve existing subscription collection
        /// </summary>
        public static SubscriptionCollection Instance {
            get {
                var collection = Subscription.ToList().FirstOrDefault(s => s._subscriptions.Count > 0);
                return collection ?? SubscriptionCollection.New;
            }
        }
        /// <summary>
        /// Return subscribers that are currently subscribed to the specified topic ID
        /// </summary>
        /// <param name="topicID"></param>
        /// <returns></returns>
        public List<ISubscriber> this[Guid topicID] {
            get {
                Dictionary<Guid, ISubscriber> subscribers;
                _subscriptions.TryGetValue(topicID, out subscribers);
                if (subscribers == null) subscribers = new Dictionary<Guid, ISubscriber>();
                var list = subscribers.Select(s => s.Value).ToList();

                if (!_wildCardTopics.Contains(topicID)) {
                    var wildSubscription = _subscriptions.Where(x => _wildCardTopics.Any(w => w == x.Key));
                    var wildSubscribers = wildSubscription.SelectMany(x => x.Value).Select(x => x.Value);
                    list = list.Union(wildSubscribers).ToList();
                }

                return list;
            }
        }

        /// <summary>
        /// Add wild card topic reference
        /// </summary>
        /// <param name="subscriber"></param>
        public void AddWildCardTopic(Guid topicID) {
            if (!_wildCardTopics.Contains(topicID))
                _wildCardTopics.Add(topicID);
        }

        /// <summary>
        /// Remove wild card topic reference
        /// </summary>
        /// <param name="topicID"></param>
        public bool RemoveWildCardTopic(Guid topicID) {
            return _wildCardTopics.Remove(topicID);
        }

        /// <summary>
        /// Get/Set to update subscription of topic for subscriber
        /// </summary>
        /// <param name="topicID"></param>
        /// <param name="subscriberID"></param>
        /// <returns></returns>
        internal ISubscriber this[Guid topicID, Guid subscriberID] {
            get {
                Dictionary<Guid, ISubscriber> subscribers;
                _subscriptions.TryGetValue(topicID, out subscribers);
                if (subscribers == null)
                    subscribers = _subscriptions[topicID] = new Dictionary<Guid, ISubscriber>();
                ISubscriber subscriber = null;
                subscribers.TryGetValue(subscriberID, out subscriber);
                return subscriber;
            }
            set {
                if (!_subscriptions.ContainsKey(topicID)) _subscriptions[topicID] = new Dictionary<Guid, ISubscriber>();
                if (value == null) {
                    if (_subscriptions[topicID].ContainsKey(subscriberID))
                        _subscriptions[topicID].Remove(subscriberID);
                } else
                    _subscriptions[topicID][subscriberID] = value;
            }
        }

        public void UpdateSubscriber(Guid topicID, Guid subscriberID, ISubscriber subscriber) {
            this[topicID, subscriberID] = subscriber;
        }

        /// <summary>
        /// Commit the changes with subscription of subscribers and topics 
        /// </summary>
        /// <param name="changes"></param>
        internal static void Update(Dictionary<Guid, Dictionary<Guid, ISubscriber>> changes) {
            if (changes.Count < 1) return;
            var mapping = Instance;
            foreach (var c in changes) {
                foreach (var d in c.Value) {
                    if (mapping._subscriptions.ContainsKey(c.Key)) {
                        if (mapping._subscriptions[c.Key].ContainsKey(d.Key) && changes[c.Key][d.Key] == null) {
                            mapping._subscriptions[c.Key].Remove(d.Key);
                            continue;
                        }
                    }
                    if (d.Value != null) mapping[c.Key, d.Key] = d.Value;
                }
            }
            Subscription.Refresh(mapping);
        }

        private static IObjectManager<SubscriptionCollection> _subscriptionCollection;
        private static IObjectManager<SubscriptionCollection> Subscription {
            get {
                if (_subscriptionCollection == null) _subscriptionCollection = ObjectManager<SubscriptionCollection>.Instance;
                return _subscriptionCollection;
            }
        }

        /// <summary>
        /// Refresh subscription collection with new changes
        /// </summary>
        /// <param name="changes"></param>
        internal static void Refresh(SubscriptionCollection changes) {
            Update(changes._subscriptions);
        }

        public void Save() {
            Subscription.Refresh(this);
        }

        #region IIdentifiable Members

        public Guid ID { get; set; }

        #endregion
    }
}
